using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace CodePlex.Resourceful.CommandLine
{
    class Program
    {

        private static List<ServiceInfo> _services
         = new List<ServiceInfo>(Services.All());

        
        static void Main(string[] args)
        {
            int exitCode = Go(args);

            Environment.Exit(exitCode);

        }

     

        private static int Go(string[] args)
        {
            Console.Out.WriteLine("--------------------------------------------------");
            Console.Out.WriteLine("{0} {1} {2}",
                Assembly.GetEntryAssembly().GetName().Name,
              //  Assembly.GetEntryAssembly().GetName().Version.ToString(),
               ((AssemblyFileVersionAttribute) Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyFileVersionAttribute),true)[0]).Version,
                Client.ProjectUri);
            Console.Out.WriteLine("--------------------------------------------------");

            if (args == null)
            {
                return Error(1, null, "args is null");
            }

   
            if (args.Length == 0)
            {
                return Help(PrintSupportedServices);
            }

            string service = (args[0] ?? string.Empty).ToLower().Trim();

            if (service == "help")
            {
                return Help(PrintSupportedServices);
            }


            ServiceInfo si = Utils.Find(_services, delegate(ServiceInfo item)
            {
                return item.Name == service;
            });

            if (si == null)
            {
                return Error(1, PrintSupportedServices, "Unknown service: {0}", service);
            }

            if (args.Length == 1)
            {
                return Help(PrintSupportedActions(si));
            }

            string action = (args[1] ?? string.Empty).ToLower().Trim();

            if (action == "help")
            {
                return Help(PrintSupportedActions(si));
            }

            ActionInfo ai = Utils.Find(si.Actions, delegate(ActionInfo item)
           {
               return item.Name == action;
           });

            if (ai == null)
            {
                return Error(1, PrintSupportedActions(si), "Unknown action: {0}", action);
            }


            string next = args.Length == 2?string.Empty:(args[2] ?? string.Empty).ToLower().Trim();

            if (next == "help")
            {
                return Help(PrintSupportedParameters(si,ai));
            }

            NameValuePairs nvp;
            try
            {
                // validate required parameters
                 nvp = ParseParameters(ai, Utils.Subarray(args, 2));
            }
            catch (Exception ex)
            {
                return Error(1,PrintSupportedParameters(si, ai),ex.Message);
            }

            try
            {
                ai.Go(nvp);
            }
            catch (Exception ex)
            {
                return Error(1, null, ex.Message);
            }

            return 0;
        }


        private static NameValuePairs ParseParameters(ActionInfo ai, IEnumerable<string> args)
        {
            NameValuePairs nvp = new NameValuePairs();

            foreach (string arg in args)
            {
                int i = arg.IndexOf(':');
                if (i == -1)
                {
                    throw CodePlexException.Format("Invalid parameter: {0}\r\n Parameters should be name:value", arg);
                }

                string name = arg.Substring(0, i).ToLower().Trim();
                string value = i==arg.Length-1?string.Empty:arg.Substring(i + 1);


                if (string.IsNullOrEmpty(name))
                {
                    throw CodePlexException.Format("Invalid parameter: {0}\r\n Name cannot be blank", arg);
                }

                if (nvp.ContainsKey(name))
                {
                    throw CodePlexException.Format("Invalid parameter: {0} specified more than once", name);
                }




                nvp[name.ToLower()] = value;
            }

            foreach (ParameterInfo pi in ai.RequiredParameters)
            {
                if (!nvp.ContainsKey(pi.Name) && pi.DefaultValue == null)
                {
                    throw CodePlexException.Format("Required parameter {0} not found", pi.Name);
                }
            }



            return nvp;
        }

        private static Operation PrintSupportedActions(ServiceInfo si)
        {
            return delegate
            {
                Console.Out.WriteLine();
                Console.Out.WriteLine("Service {0} supports the following actions:", si.Name);


                int maxLength = MaxNameLength(si.Actions);
                
                foreach (ActionInfo ai in si.Actions)
                {
                    Console.Out.WriteLine("    {0,-" + maxLength + "}   {1}", ai.Name, ai.Description);
                }
            };
        }

        private static int MaxNameLength<T>(IEnumerable<T> values)
            where T : BaseInfo
        {
            return Utils.Max(values, delegate(T lhs, T rhs)
              {
                  return lhs.Name.Length.CompareTo(rhs.Name.Length);
              }).Name.Length;
        }

        private static Operation PrintSupportedParameters(ServiceInfo si, ActionInfo ai)
        {
            return delegate
            {
                Console.Out.WriteLine();
                Console.Out.WriteLine("Service {0} action {1} supports the following parameters:",si.Name,  ai.Name);

                int maxLength = MaxNameLength(ai.Parameters);

                foreach (ParameterInfo pi in ai.Parameters)
                {
                    Console.Out.WriteLine("    {0,-" + maxLength + "}   {1}   {2}", pi.Name, (pi.IsRequired?"(required)":"(optional)"), pi.Description);
                }
            };
        }

       


     

        private static int Help(Operation extraInfo)
        {
            Console.Out.WriteLine("Usage:  rescmd <service> <action> <parameters>...");
            Console.Out.WriteLine();
            Console.Out.WriteLine("Help on a specific service:   rescmd <service> help");
            Console.Out.WriteLine("Help on a specific action:    rescmd <service> <action> help");
            if (extraInfo != null)
            {
                extraInfo();
            }
            return 0;
        }

        private static void PrintSupportedServices()
        {
            Console.Out.WriteLine();
            Console.Out.WriteLine("rescmd supports the following services:");
            foreach (ServiceInfo si in _services)
            {
                Console.Out.WriteLine("    {0}   {1}", si.Name, si.Description);
            }
        }

        private static int Error(int code, Operation extraInfo,  string message, params object[] args)
        {
            Console.Out.WriteLine("ERROR: " + string.Format(message, args));

            if (extraInfo != null)
            {
                extraInfo();
            }
            return code;
        }

    }
}
